﻿using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Security;
using System.Security.Policy;
using System.Security.Cryptography.X509Certificates;
using System.Security.Cryptography;
using HtmlAgilityPack;

namespace JuniperScanner.Core
{
    class JuniperManager : IDisposable
    {
        #region Fields

        public static List<ResponseDetails> ResponseDetails { get; set; }
        public static ResponseSummary ResponseSummary { get; set; }
        private static List<string> existingSignInUrls;
        private static Mutex _mutex;
        private static string _baseUrl;
        private Task<bool>[] _threads;
        //test urls
        
        private readonly string AdminAccessUrl;
        private readonly string FirstFileUrl;
        private readonly string SecondFileUrl;
        private readonly string MeetingUrl;
        private readonly string FirstRemediateUrl;
        private readonly string SecondRemediateUrl;
        private readonly string DefaultAttackUrl;
        private readonly string AuthByPassUrl;
        private readonly string XssVulnerabilityUr;
        private readonly string C_XssVulnerabilityUr;
        private readonly string D_XssVulnerabilityUr;
        private readonly string E_XssVulnerabilityUr;
        private readonly string F_XssVulnerabilityUr;
        private readonly string G_XssVulnerabilityUr;


        #endregion Fields

        #region Constructors

        public JuniperManager(string baseUrl)
        {
            _baseUrl = baseUrl;
            AdminAccessUrl = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.AdminAccessUrlPart);
            FirstFileUrl = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.FirstFileUrlPart);
            SecondFileUrl = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.SecondFileUrlPart);
            MeetingUrl = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.Meeting);
            FirstRemediateUrl = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.FirstRemediateCgiUrlPart);
            SecondRemediateUrl = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.SecondRemediateCgiUrlPart);
            DefaultAttackUrl = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.ScriptingAttacksUrlPartDefault);
            AuthByPassUrl = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.AuthByPassUrlPart);
            XssVulnerabilityUr = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.XssVulnerabilityUrlPart);
            C_XssVulnerabilityUr = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.CXssVulnerabilityUrlPart);
            D_XssVulnerabilityUr = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.DXssVulnerabilityUrlPart);
            E_XssVulnerabilityUr = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.EXssVulnerabilityUrlPart);
            F_XssVulnerabilityUr = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.FXssVulnerabilityUrlPart);
            G_XssVulnerabilityUr = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.GBXssVulnerabilityUrlPart);
        }

        #endregion Constructors

        #region Methods

        public void Dispose()
        {
            if (_threads != null)
            {
                Task.WaitAll(_threads);

                ResponseDetails = null;
                existingSignInUrls = null;
                ResponseSummary = null;
                _threads = null;
            }
        }

        public static string GetResultForExport()
        {
            var result = new StringBuilder();

            foreach (var item in ResponseDetails)
            {
                result.Append(String.Format("{0}{1}{2}{3}{4}{5}", item.Url, '\t', item.Response, '\t', item.Size, '\n'));
            }

            return result.ToString();
        }

        public void CheckResource(ScannerSetting setting)
        {
            if (String.IsNullOrEmpty(_baseUrl))
            {
                return;
            }

            ResponseDetails = new List<ResponseDetails>();
            ResponseSummary = new Core.ResponseSummary();
            existingSignInUrls = new List<string>();
            _mutex = new Mutex(false);
            

            this.PerformTests(setting);
        }

        private void PerformTests(ScannerSetting setting)
        {
            if (ResponseSummary == null) //|| ResponseDetails == null) 
            {
                return;
            }

            if (setting.CheckMultipleSignInPages)
            {
                this.PerformMultiplySignInPagesTest(setting);

                ResponseSummary.SignInPagesOk = this.CollectSummaryItem(Categories.SignInPages, true);

                Core.ResponseDetails.ClearBody(ResponseDetails, true);
            }

            if (setting.CheckAdminAccess)
            {
                this.PerformAdminAccessTest();

                ResponseSummary.AdminAccessOk = this.CollectSummaryItem(Categories.AdminAccess, false);
            }

            if (setting.CheckMeetingTest) 
            {
                this.PerformCheckMeetingTest();

                ResponseSummary.MeetingTestOk = this.CollectSummaryItem(Categories.MeetingTest, false);
            }

            if (setting.CheckXssVulnerability)
            {
                this.PerformScriptingAttacksTest();

                this.PerformXssVulnerabilityTests();

                ResponseSummary.XssTestOk = this.CollectSummaryItem(Categories.XssTest, true);
            }

            if (setting.CheckSetupFiles)
            {
                this.PerformCheckFilesExistenceTest();

                ResponseSummary.SetupFilesOk = this.CollectSummaryItem(Categories.SetupFiles, true);
            }

            if (setting.CheckWebRoot)
            {
                this.PerformCheckWebRootTest();

                ResponseSummary.WebRootOk = this.CollectSummaryItem(Categories.WebRoot, false);
            }

            if (setting.CheckAuthByPass) 
            {
                this.PerformAuthByPassTest();

                ResponseSummary.AuthByPassOk = this.CollectSummaryItem(Categories.AuthByPass, false);
            }
        }

        public static bool ScanResource(object url)
        {
            var completedUrl = new Uri((string)url);
            var request = ConfigureRequest(completedUrl);
            var details = new ResponseDetails
                               {
                                    Url = completedUrl,
                                    Category = Categories.SignInPages
                               };

            try
            {
                using (var response = (HttpWebResponse)request.GetResponse())
                {
                    WriteResponseInfo(response, details);
                }
            }
            catch (WebException ex)
            {
                WriteResponseInfo((HttpWebResponse)ex.Response, details);
            }

            return true;
        }

        private static void WriteResponseInfo(HttpWebResponse response, ResponseDetails details)
        {
            details.Response = String.Format("{0}{1}{2}", Convert.ToString((int)response.StatusCode), ' ', response.StatusDescription);
            details.Size = response.ContentLength == -1 ? "Not Presented" : response.ContentLength.ToString();

            using (var stream = response.GetResponseStream()) 
            {
                using (var reader = new StreamReader(stream, Encoding.Default)) 
                {
                    details.Body = reader.ReadToEnd();
                }
            }

            if (ResponseDetails != null)
            {
                //Critical section
                _mutex.WaitOne();
                ResponseDetails.Add(details);
                _mutex.ReleaseMutex();
            }
        }

        private static HttpWebRequest ConfigureRequest(Uri url)
        {
            AllowInvalidCertificate();
            var request = (HttpWebRequest)WebRequest.Create(url);
            request.UserAgent = "Mozilla/5.0 (Windows NT 6.1; rv:5.0) Gecko/20100101 Firefox/5.0";
            request.Headers.Add("Accept-Language", "ru-Ru");
            request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            request.Method = "GET";
            request.KeepAlive = false;
            request.Proxy = null;
            request.Credentials = CredentialCache.DefaultCredentials;

            return request;
        }

        private void CollectOkAnswers()
        {
            foreach (var item in ResponseDetails)
            {
                if (item.Response.Contains("200") && item.Category.Equals(Categories.SignInPages))
                {
                    existingSignInUrls.Add(item.Url.ToString());
                }
            }
        }

        private bool CollectSummaryItem(string category, bool checkForMultiplyExistence)
        {
            var result = false;

            if (ResponseDetails != null )
            {
                int counter = 0;
                foreach (var item in ResponseDetails)
                {
                    if (item.Category.Equals(category) && item.Response.Contains("200"))
                    {
                        result = true;
                        counter++;

                        if (!checkForMultiplyExistence)
                        {
                            break;
                        }
                        if (checkForMultiplyExistence && 
                            counter > 1 && 
                            category.Equals(Categories.SignInPages))
                        {
                            result = false;
                            break;
                        }
                        if (checkForMultiplyExistence &&
                            counter > 1 &&
                            category.Equals(Categories.SetupFiles)) 
                        {
                            break;
                        }
                    }
                    else if (item.Category.Equals(category)
                        && !item.Response.Contains("200")
                        && checkForMultiplyExistence
                        && !item.Category.Equals(Categories.SignInPages))
                    {
                        break;
                    }
                }
            }

            return result;
        }

        #region Tests

        private void PerformMultiplySignInPagesTest(ScannerSetting setting)
        {
            _threads = new Task<bool>[setting.MaxId];

            for (int i = setting.MinId; i < setting.MaxId; i++)
            {
                var url = String.Format("{0}{1}", _baseUrl, Properties.Settings.Default.CheckByIdUrlPart).Replace("$URLID", i.ToString());

                _threads[i] = Task<bool>.Factory.StartNew(ScanResource, url);
            }

            Task.WaitAll(_threads);
        }

        private void PerformAdminAccessTest()
        {
            this.PerformSpecificTest(AdminAccessUrl, "Administrator Sign-In Page", "body", Categories.AdminAccess);
        }

        private void PerformAuthByPassTest()
        {
            ScanResource(AuthByPassUrl);
            ResponseDetails[ResponseDetails.Count - 1].Category = Categories.AuthByPass;
            Core.ResponseDetails.ClearBody(ResponseDetails, false);
        }

        private void PerformScriptingAttacksTest()
        {
            //checking url_default
            this.PerformSpecificTest(DefaultAttackUrl, "alert(1)", "script", Categories.XssTest);

            //checking existing url
            this.CollectOkAnswers();

            if (existingSignInUrls.Count != 0)
            {
                foreach (var item in existingSignInUrls)
                {
                    var url = String.Format("{0}{1}", item, Properties.Settings.Default.ScriptingAttackUrlPartCustom);
                    this.PerformSpecificTest(url, "alert(1)", "script", Categories.XssTest);
                }
            }
        }

        private void PerformXssVulnerabilityTests()
        {
            this.PerformSpecificTest(XssVulnerabilityUr, "alert(document.cookie)", "script", Categories.XssTest);
            this.PerformSpecificTest(C_XssVulnerabilityUr, "alert(999)", "script", Categories.XssTest);
            this.PerformSpecificTest(D_XssVulnerabilityUr, "alert(999)", "script", Categories.XssTest);
            this.PerformSpecificTest(E_XssVulnerabilityUr, "alert(999)", "script", Categories.XssTest);
            this.PerformSpecificTest(F_XssVulnerabilityUr, "alert(999)", "script", Categories.XssTest);
            this.PerformSpecificTest(G_XssVulnerabilityUr, "alert(999)", "script", Categories.XssTest);
        }

        private void PerformCheckMeetingTest()
        {
            Core.ResponseDetails.ClearBody(ResponseDetails, false);
            this.PerformSpecificTest(MeetingUrl, "Meeting User Sign-In Page.", "body", Categories.MeetingTest);
        }

        private void PerformCheckFilesExistenceTest()
        {
            ScanResource(FirstFileUrl);
            Core.ResponseDetails.ClearBody(ResponseDetails, false);
            ResponseDetails[ResponseDetails.Count - 1].Category = Categories.SetupFiles;
            ScanResource(SecondFileUrl);
            Core.ResponseDetails.ClearBody(ResponseDetails, false);
            ResponseDetails[ResponseDetails.Count - 1].Category = Categories.SetupFiles;
        }

        private void PerformCheckWebRootTest()
        {
            this.PerformSpecificTest(FirstRemediateUrl, "500 Internal Error", "H2", Categories.WebRoot);
            this.PerformSpecificTest(SecondRemediateUrl, "500 Internal Error", "H2", Categories.WebRoot);
        }

        private void PerformSpecificTest(string url, string searchText, string searchTag, string category)
        {
            ScanResource(url);

            if (ResponseDetails == null) 
            {
                return;
            }

            var details = ResponseDetails[ResponseDetails.Count - 1];
            details.Category = category;

            SearchSpecificContent(details, searchText, searchTag);
        }

        private static void SearchSpecificContent(ResponseDetails details, string searchText, string searchTag)
        {
            HtmlAgilityPack.HtmlDocument doc = new HtmlDocument();
            doc.LoadHtml(details.Body);

            var body = doc.DocumentNode.SelectNodes(String.Format("{0}{1}", "//", searchTag));
            details.Body = "Did not exists";

            if (body != null)
            {
                foreach (var item in body)
                {
                    if (item.InnerText.Contains(searchText))
                    {
                        details.Body = String.Format("{0}{1}", searchText, " exists");
                        break;
                    }
                }
            }
        }

        #endregion Tests

        #region https_hack

        public static void AllowInvalidCertificate()
        {
            ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(allowCert);
        }

        private static bool allowCert(object sender, X509Certificate cert, X509Chain chain, SslPolicyErrors error)
        {
            return true;
        }

        #endregion https_hack

        #endregion Methods
    }
}
